/* ----------
 * pgstat.c
 *	  累积统计系统的基础设施。
 *
 * 累积统计系统为不同类型的对象收集统计信息。某些类型的统计信息是为固定数量的对象（最常见的是1）收集的，例如，检查点统计信息。其他类型的统计信息是为不同数量的对象（例如关系）收集的。有关当前处理的统计信息列表，请参见PgStat_KindInfo。
 *
 * 统计信息在启动期间从文件系统加载（由启动进程），除非先前发生崩溃，此情况下所有统计都被丢弃。它们在关闭前，由检查点进程写出，除了在即时模式下关闭时。
 *
 * 固定编号的统计信息存储在普通（非动态）共享内存中。
 *
 * 可变编号的对象的统计信息存储在动态共享内存中，并可以通过dshash哈希表找到。统计计数器不是dshash条目（PgStatShared_HashEntry）的一部分，而是单独分配的（PgStatShared_HashEntry->body）。单独分配允许以相同的哈希表存储不同类型的统计信息，而不会在PgStatShared_HashEntry中浪费空间。
 *
 * 可变编号的统计信息在运行时通过PgStat_HashKey寻址。在运行时无法寻址的对象是无法拥有统计信息的。序列化到磁盘时，可以使用更宽的标识符（用于复制槽统计信息）。
 *
 * 为避免在共享哈希表上发生竞争，每个后端在共享哈希表前面有一个后端本地哈希表（pgStatEntryRefHash），其中包含对共享哈希表条目的引用（PgStat_EntryRef）。只有在本地哈希表中找不到先前的引用时，才需访问共享哈希表。除了指向共享哈希表条目（PgStatShared_HashEntry），PgStat_EntryRef还包含对共享统计数据的指针，以进程本地地址减少访问成本。
 *
 * 存储在共享内存中的结构的名称以PgStatShared而不是PgStat为前缀。共享内存中的每个统计条目均由专用的lwlock保护。
 *
 * 大多数统计更新首先在每个进程中作为待处理条目本地累积，然后在提交后或通过空闲超时刷新到共享内存。这实际上消除了对单个统计条目的竞争。对于大多数类型的可变编号的待处理统计数据存储在PgStat_EntryRef->pending中。所有具有待处理数据的条目均位于pgStatPending列表中。待处理统计更新由pgstat_report_stat()刷新。
 *
 * 不同类型统计行为通过pgstat_kind_infos中的类型条目来决定，详细信息见PgStat_KindInfo。
 *
 * 对统计的读取一致性可通过stats_fetch_consistency GUC进行配置（见config.sgml和monitoring.sgml的设置）。当使用PGSTAT_FETCH_CONSISTENCY_CACHE或PGSTAT_FETCH_CONSISTENCY_SNAPSHOT时，统计信息存储在pgStatLocal.snapshot中。
 *
 * 为了保持可管理，统计处理拆分到多个文件中。基础设施部分在：
 * - pgstat.c - 本文件，用于将其全部连接在一起
 * - pgstat_shmem.c - 几乎所有处理共享内存的内容，包括哈希表条目的维护
 * - pgstat_xact.c - 事务集成，包括统计条目的事务创建和删除
 *
 * 每种统计类型在专用文件中处理：
 * - pgstat_archiver.c
 * - pgstat_bgwriter.c
 * - pgstat_checkpointer.c
 * - pgstat_database.c
 * - pgstat_function.c
 * - pgstat_relation.c
 * - pgstat_replslot.c
 * - pgstat_slru.c
 * - pgstat_subscription.c
 * - pgstat_wal.c
 *
 * 基础设施文件应尽可能不包含与具体类型统计相关的代码。
 *
 *
 * Copyright (c) 2001-2022, PostgreSQL Global Development Group
 *
 * IDENTIFICATION
 *	  src/backend/utils/activity/pgstat.c
 * ----------
 */
#include "postgres.h"

#include <unistd.h>

#include "access/transam.h"
#include "access/xact.h"
#include "lib/dshash.h"
#include "pgstat.h"
#include "port/atomics.h"
#include "storage/fd.h"
#include "storage/ipc.h"
#include "storage/lwlock.h"
#include "storage/pg_shmem.h"
#include "storage/shmem.h"
#include "utils/guc.h"
#include "utils/memutils.h"
#include "utils/pgstat_internal.h"
#include "utils/timestamp.h"


/* ----------
 * 定时器定义。
 *
 * 以毫秒为单位。
 * ----------
 */

/* 最小间隔非强制统计刷新。*/
#define PGSTAT_MIN_INTERVAL			1000
/* 何时阻塞刷新的待处理统计更新 */
#define PGSTAT_MAX_INTERVAL			60000
/* 何时再次调用 pgstat_report_stat()，即使在空闲时 */
#define PGSTAT_IDLE_INTERVAL		10000

/* ----------
 * 用于统计信息的哈希表的初始大小提示。
 * ----------
 */

#define PGSTAT_SNAPSHOT_HASH_SIZE	512


/* 统计快照条目的哈希表 */
typedef struct PgStat_SnapshotEntry
{
	PgStat_HashKey key;
	char		status;			/* 用于 simplehash */
	void	   *data;			/* 统计数据本身 */
} PgStat_SnapshotEntry;


/* ----------
 * 后端本地哈希表定义
 * ----------
 */

/* 用于统计快照条目 */
#define SH_PREFIX pgstat_snapshot
#define SH_ELEMENT_TYPE PgStat_SnapshotEntry
#define SH_KEY_TYPE PgStat_HashKey
#define SH_KEY key
#define SH_HASH_KEY(tb, key) \
	pgstat_hash_hash_key(&key, sizeof(PgStat_HashKey), NULL)
#define SH_EQUAL(tb, a, b) \
	pgstat_cmp_hash_key(&a, &b, sizeof(PgStat_HashKey), NULL) == 0
#define SH_SCOPE static inline
#define SH_DEFINE
#define SH_DECLARE
#include "lib/simplehash.h"


/* ----------
 * 本地函数前向声明
 * ----------
 */

static void fc_pgstat_write_statsfile(void);
static void fc_pgstat_read_statsfile(void);

static void fc_pgstat_reset_after_failure(void);

static bool fc_pgstat_flush_pending_entries(bool fc_nowait);

static void fc_pgstat_prep_snapshot(void);
static void fc_pgstat_build_snapshot(void);
static void fc_pgstat_build_snapshot_fixed(PgStat_Kind fc_kind);

static inline bool fc_pgstat_is_kind_valid(int fc_ikind);


/* ----------
 * GUC 参数
 * ----------
 */

bool		pgstat_track_counts = false;
int			pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_CACHE;


/* ----------
 * 与 pgstat_*.c 共享的状态
 * ----------
 */

PgStat_LocalState pgStatLocal;


/* ----------
 * 本地数据
 *
 * 注意：这里应仅有与统计基础设施相关的变量，
 * 不应有特定类型的统计数据。
 * ----------
 */

/*
 * 包含 pgStatEntryRefHash 表、pgStatSharedRef 条目和待处理数据的内存上下文。
 * 主要是为了更容易跟踪/归因内存使用。
 */

static MemoryContext pgStatPendingContext = NULL;

/*
 * 后端本地的 PgStat_EntryRef 列表，包含未刷新待处理的统计数据。
 *
 * 新的待处理条目应仅添加到列表的末尾，
 * 否则 pgstat_flush_pending_entries() 可能不会立即看到它们。
 */
static dlist_head pgStatPending = DLIST_STATIC_INIT(pgStatPending);


/*
 * 强制下一次统计刷新发生，无论
 * PGSTAT_MIN_INTERVAL。对测试脚本很有用。
 */
static bool pgStatForceNextFlush = false;

/*
 * 在更改 stats_fetch_consistency 时，强制清除现有快照，以便下次使用。
 */
static bool force_stats_snapshot_clear = false;


/*
 * 用于检查 pgstat 在初始化前/关闭后未被使用的断言。
 */
#ifdef USE_ASSERT_CHECKING
static bool pgstat_is_initialized = false;
static bool pgstat_is_shutdown = false;
#endif


/*
 * 各种统计信息的类型。
 *
 * 如果合理可行，处理特定于某种统计类型的操作应通过这种抽象进行，
 * 而不是让更多的 pgstat.c 感知。
 *
 * 有关各个字段的详细信息，请参阅 struct PgStat_KindInfo 的注释。
 *
 * XXX：将其定义在这个文件之外会更好。但考虑到多个
 * 文件之间的分割，似乎没有很好的办法做到这一点。
 */
static const PgStat_KindInfo pgstat_kind_infos[PGSTAT_NUM_KINDS] = {

	/* 可变编号对象的统计类型 */

	[PGSTAT_KIND_DATABASE] = {
		.name = "database",

		.fixed_amount = false,
		/* 使 pg_stat_database 条目可以在所有数据库中查看 */
		.accessed_across_databases = true,

		.shared_size = sizeof(PgStatShared_Database),
		.shared_data_off = offsetof(PgStatShared_Database, stats),
		.shared_data_len = sizeof(((PgStatShared_Database *) 0)->stats),
		.pending_size = sizeof(PgStat_StatDBEntry),

		.flush_pending_cb = pgstat_database_flush_cb,
		.reset_timestamp_cb = pgstat_database_reset_timestamp_cb,
	},

	[PGSTAT_KIND_RELATION] = {
		.name = "relation",

		.fixed_amount = false,

		.shared_size = sizeof(PgStatShared_Relation),
		.shared_data_off = offsetof(PgStatShared_Relation, stats),
		.shared_data_len = sizeof(((PgStatShared_Relation *) 0)->stats),
		.pending_size = sizeof(PgStat_TableStatus),

		.flush_pending_cb = pgstat_relation_flush_cb,
		.delete_pending_cb = pgstat_relation_delete_pending_cb,
	},

	[PGSTAT_KIND_FUNCTION] = {
		.name = "function",

		.fixed_amount = false,

		.shared_size = sizeof(PgStatShared_Function),
		.shared_data_off = offsetof(PgStatShared_Function, stats),
		.shared_data_len = sizeof(((PgStatShared_Function *) 0)->stats),
		.pending_size = sizeof(PgStat_BackendFunctionEntry),

		.flush_pending_cb = pgstat_function_flush_cb,
	},

	[PGSTAT_KIND_REPLSLOT] = {
		.name = "replslot",

		.fixed_amount = false,

		.accessed_across_databases = true,
		.named_on_disk = true,

		.shared_size = sizeof(PgStatShared_ReplSlot),
		.shared_data_off = offsetof(PgStatShared_ReplSlot, stats),
		.shared_data_len = sizeof(((PgStatShared_ReplSlot *) 0)->stats),

		.reset_timestamp_cb = pgstat_replslot_reset_timestamp_cb,
		.to_serialized_name = pgstat_replslot_to_serialized_name_cb,
		.from_serialized_name = pgstat_replslot_from_serialized_name_cb,
	},

	[PGSTAT_KIND_SUBSCRIPTION] = {
		.name = "subscription",

		.fixed_amount = false,
		/* 使 pg_stat_subscription_stats 条目可以在所有数据库中查看 */
		.accessed_across_databases = true,

		.shared_size = sizeof(PgStatShared_Subscription),
		.shared_data_off = offsetof(PgStatShared_Subscription, stats),
		.shared_data_len = sizeof(((PgStatShared_Subscription *) 0)->stats),
		.pending_size = sizeof(PgStat_BackendSubEntry),

		.flush_pending_cb = pgstat_subscription_flush_cb,
		.reset_timestamp_cb = pgstat_subscription_reset_timestamp_cb,
	},


	/* 固定编号（主要为1）对象的统计数据 */

	[PGSTAT_KIND_ARCHIVER] = {
		.name = "archiver",

		.fixed_amount = true,

		.reset_all_cb = pgstat_archiver_reset_all_cb,
		.snapshot_cb = pgstat_archiver_snapshot_cb,
	},

	[PGSTAT_KIND_BGWRITER] = {
		.name = "bgwriter",

		.fixed_amount = true,

		.reset_all_cb = pgstat_bgwriter_reset_all_cb,
		.snapshot_cb = pgstat_bgwriter_snapshot_cb,
	},

	[PGSTAT_KIND_CHECKPOINTER] = {
		.name = "checkpointer",

		.fixed_amount = true,

		.reset_all_cb = pgstat_checkpointer_reset_all_cb,
		.snapshot_cb = pgstat_checkpointer_snapshot_cb,
	},

	[PGSTAT_KIND_SLRU] = {
		.name = "slru",

		.fixed_amount = true,

		.reset_all_cb = pgstat_slru_reset_all_cb,
		.snapshot_cb = pgstat_slru_snapshot_cb,
	},

	[PGSTAT_KIND_WAL] = {
		.name = "wal",

		.fixed_amount = true,

		.reset_all_cb = pgstat_wal_reset_all_cb,
		.snapshot_cb = pgstat_wal_snapshot_cb,
	},
};


/* ------------------------------------------------------------
 * 管理所有后端统计系统状态的函数。
 * ------------------------------------------------------------
 */

/*
 * 在服务器启动时将磁盘上的统计信息读取到内存中。
 *
 * 应仅由启动进程或单用户模式下调用。
 */
void pgstat_restore_stats(void)
{
	fc_pgstat_read_statsfile();
}

/*
 * 删除统计文件。当前仅在崩溃后需要 WAL 恢复时使用。
 *
 * 应仅由启动进程或单用户模式下调用。
 */
void pgstat_discard_stats(void)
{
	int			fc_ret;

	/* 注意：即使在单用户模式下也需要完成此操作 */

	fc_ret = unlink(PGSTAT_STAT_PERMANENT_FILENAME);
	if (fc_ret != 0)
	{
		if (errno == ENOENT)
			elog(DEBUG2,
				 "didn't need to unlink permanent stats file \"%s\" - didn't exist",
				 PGSTAT_STAT_PERMANENT_FILENAME);
		else
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not unlink permanent statistics file \"%s\": %m",
							PGSTAT_STAT_PERMANENT_FILENAME)));
	}
	else
	{
		ereport(DEBUG2,
				(errcode_for_file_access(),
				 errmsg_internal("unlinked permanent statistics file \"%s\"",
						PGSTAT_STAT_PERMANENT_FILENAME)));
	}

	/*
	 * 重置统计信息内容。这将设置固定编号统计信息的重置时间戳为当前时间（没有可变统计信息存在）。
	 */
	fc_pgstat_reset_after_failure();
}

/*
 * pgstat_before_server_shutdown() 需要由正好一个进程在常规服务器关闭期间调用。否则所有统计信息将会丢失。
 *
 * 我们目前仅在 proc_exit(0) 时写出统计信息。我们可能希望在某个时候更改这一点……但现在无论如何在无序关闭后启动时都会调用 pgstat_discard_stats()。
 */
void pgstat_before_server_shutdown(int fc_code, Datum fc_arg)
{
	Assert(pgStatLocal.shmem != NULL);
	Assert(!pgStatLocal.shmem->is_shutdown);

	/*
	 * 统计信息应仅在 pgstat_initialize() 之后和 pgstat_shutdown() 之前报告。这是捕捉大多数违反此规则的方便时机。
	 */
	Assert(pgstat_is_initialized && !pgstat_is_shutdown);

	/* 在写出之前清除我们自己的待处理更改 */
	pgstat_report_stat(true);

	/*
	 * 仅在正常关闭期间写出文件。在不规则关闭期间甚至不要发出我们已经关闭的信号，因为关闭序列未协调以确保该后端最后关闭。
	 */
	if (fc_code == 0)
	{
		pgStatLocal.shmem->is_shutdown = true;
		fc_pgstat_write_statsfile();
	}
}


/* ------------------------------------------------------------
 * 后端初始化/关闭函数
 * ------------------------------------------------------------
 */

/*
 * 在进程退出时关闭单个后端的统计信息报告。
 *
 * 刷新任何剩余的统计计数。如果没有这一点，在后端退出期间触发的操作（例如临时表删除）将不会被计算在内。
 */
static void fc_pgstat_shutdown_hook(int fc_code, Datum fc_arg)
{
	Assert(!pgstat_is_shutdown);
	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);

	/*
	 * 如果我们发现了自己的数据库 ID，我们可以刷新到目前为止所做的工作。否则，我们将报告一个无效的数据库 ID，所以忘掉它。（这意味着在后端启动失败期间访问 pg_database 可能永远不会被计入。）
	 */
	if (OidIsValid(MyDatabaseId))
		pgstat_report_disconnect(MyDatabaseId);

	pgstat_report_stat(true);

	/* 不应有任何待处理的更改 */
	Assert(dlist_is_empty(&pgStatPending));
	dlist_init(&pgStatPending);

	pgstat_detach_shmem();

#ifdef USE_ASSERT_CHECKING
	pgstat_is_shutdown = true;
#endif
}

/*
 * 初始化 pgstats 状态，并设置我们的进程退出钩子。由 BaseInit() 调用。
 *
 * 注意：MyDatabaseId 还没有设置；因此关闭钩子必须小心。
 */
void pgstat_initialize(void)
{
	Assert(!pgstat_is_initialized);

	pgstat_attach_shmem();

	pgstat_init_wal();

	/* 设置一个进程退出钩子以进行清理 */
	before_shmem_exit(fc_pgstat_shutdown_hook, 0);

#ifdef USE_ASSERT_CHECKING
	pgstat_is_initialized = true;
#endif
}


/* ------------------------------------------------------------
 * 后端使用的公共函数如下
 * ------------------------------------------------------------
 */

/*
 * 必须由执行 DML 的进程调用：tcop/postgres.c、逻辑接收进程、SPI 工作线程等，以将待处理的统计信息更新刷新到共享内存中。
 *
 * 除非使用 'force' 参数调用，否则待处理的统计更新每 PGSTAT_MIN_INTERVAL（1000ms） 刷新一次。当不强制操作时，统计刷新不会在锁获取时阻塞，除非统计更新已经等待超过 PGSTAT_MAX_INTERVAL（60000ms）。
 *
 * 每当待处理的统计更新保留在 pgstat_report_stat() 的末尾时，将返回建议的空闲超时。目前，这始终是 PGSTAT_IDLE_INTERVAL（10000ms）。调用者可以使用返回的时间设置超时，然后调用 pgstat_report_stat(true)，但不必这样做。
 *
 * 请注意，只有在不处于事务中时才会调用此方法，因此使用事务停止时间作为当前时间的近似值是合理的。
 */
long pgstat_report_stat(bool fc_force)
{
	static TimestampTz fc_pending_since = 0;
	static TimestampTz fc_last_flush = 0;
	bool		fc_partial_flush;
	TimestampTz fc_now;
	bool		fc_nowait;

	pgstat_assert_is_up();
	Assert(!IsTransactionOrTransactionBlock());

	/* "吸收" 强制刷新，即使没有内容需要刷新 */
	if (pgStatForceNextFlush)
	{
		fc_force = true;
		pgStatForceNextFlush = false;
	}

	/* 如果没有任务，则不要消耗时钟检查 */
	if (dlist_is_empty(&pgStatPending) &&
		!have_slrustats &&
		!pgstat_have_pending_wal())
	{
		Assert(fc_pending_since == 0);
		return 0;
	}

	/*
	 * 一旦统计信息关闭，就不应该有统计数据报告。在上述检查之前无法断言，因为 pgstat_shutdown_hook() 中有一个无条件的 pgstat_report_stat() 调用 - 至少运行 pgstat_before_server_shutdown() 的进程仍会调用它。
	 */
	Assert(!pgStatLocal.shmem->is_shutdown);

	fc_now = GetCurrentTransactionStopTimestamp();

	if (!fc_force)
	{
		if (fc_pending_since > 0 &&
			TimestampDifferenceExceeds(fc_pending_since, fc_now, PGSTAT_MAX_INTERVAL))
		{
			/* 不要将待处理更新保持超过 PGSTAT_MAX_INTERVAL */
			fc_force = true;
		}
		else if (fc_last_flush > 0 &&
				 !TimestampDifferenceExceeds(fc_last_flush, fc_now, PGSTAT_MIN_INTERVAL))
		{
			/* 不要刷新得太频繁 */
			if (fc_pending_since == 0)
				fc_pending_since = fc_now;

			return PGSTAT_IDLE_INTERVAL;
		}
	}

	pgstat_update_dbstats(fc_now);

	/* 当 !force 时，不要等待锁获取 */
	fc_nowait = !fc_force;

	fc_partial_flush = false;

	/* 刷新数据库 / 关系 / 函数 / ... 统计信息 */
	fc_partial_flush |= fc_pgstat_flush_pending_entries(fc_nowait);

	/* 刷新 wal 统计信息 */
	fc_partial_flush |= pgstat_flush_wal(fc_nowait);

	/* 刷新 SLRU 统计信息 */
	fc_partial_flush |= pgstat_slru_flush(fc_nowait);

	fc_last_flush = fc_now;

	/*
	 * 如果由于锁竞争而无法刷新某些待处理的统计信息，则通知调用者何时重试。
	 */
	if (fc_partial_flush)
	{
		/* 强制应该阻止我们到达这里 */
		Assert(!fc_force);

		/* 记住自何时起统计信息处于待处理状态 */
		if (fc_pending_since == 0)
			fc_pending_since = fc_now;

		return PGSTAT_IDLE_INTERVAL;
	}

	fc_pending_since = 0;

	return 0;
}

/*
 * 强制在下次 pgstat_report_stat() 调用期间刷新本地待处理的统计信息。这对于编写测试很有用。
 */
void pgstat_force_next_flush(void)
{
	pgStatForceNextFlush = true;
}

/*
 * 仅供 pgstat_reset_counters() 使用
 */
static bool fc_match_db_entries(PgStatShared_HashEntry *fc_entry, Datum fc_match_data)
{
	return fc_entry->key.dboid == DatumGetObjectId(MyDatabaseId);
}

/*
 * 重置我们数据库的计数器。
 *
 * 此函数的权限检查通过正常的 GRANT 系统管理。
 */
void pgstat_reset_counters(void)
{
	TimestampTz fc_ts = GetCurrentTimestamp();

	pgstat_reset_matching_entries(fc_match_db_entries,
								  ObjectIdGetDatum(MyDatabaseId),
								  fc_ts);
}

/*
 * 重置单个变量编号条目。
 *
 * 如果统计种类在数据库内，还将重置数据库的 stat_reset_timestamp。
 *
 * 此函数的权限检查通过正常的 GRANT 系统管理。
 */
void pgstat_reset(PgStat_Kind fc_kind, Oid fc_dboid, Oid fc_objoid)
{
	const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);
	TimestampTz fc_ts = GetCurrentTimestamp();

	/* 目前不需要，并且与当前签名没有意义 */
	Assert(!pgstat_get_kind_info(fc_kind)->fixed_amount);

	/* 重置“单个计数器” */
	pgstat_reset_entry(fc_kind, fc_dboid, fc_objoid, fc_ts);

	if (!fc_kind_info->accessed_across_databases)
		pgstat_reset_database_timestamp(fc_dboid, fc_ts);
}

/*
 * 重置所有同类条目的统计信息。
 *
 * 此函数的权限检查通过正常的 GRANT 系统管理。
 */
void pgstat_reset_of_kind(PgStat_Kind fc_kind)
{
	const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);
	TimestampTz fc_ts = GetCurrentTimestamp();

	if (fc_kind_info->fixed_amount)
		fc_kind_info->reset_all_cb(fc_ts);
	else
		pgstat_reset_entries_of_kind(fc_kind, fc_ts);
}


/* ------------------------------------------------------------
 * 获取统计信息
 * ------------------------------------------------------------
 */

/*
 * 丢弃在当前事务中收集的任何数据。任何后续请求将导致读取新的快照。
 *
 * 这也在事务提交或中止期间被调用，以丢弃不再需要的快照。
 * stats_fetch_consistency 的更新可能会导致调用此例程。
 */
void pgstat_clear_snapshot(void)
{
	pgstat_assert_is_up();

	memset(&pgStatLocal.snapshot.fixed_valid, 0,
		   sizeof(pgStatLocal.snapshot.fixed_valid));
	pgStatLocal.snapshot.stats = NULL;
	pgStatLocal.snapshot.mode = PGSTAT_FETCH_CONSISTENCY_NONE;

	/* 释放内存（如果有分配的话） */
	if (pgStatLocal.snapshot.context)
	{
		MemoryContextDelete(pgStatLocal.snapshot.context);

		/* 重置变量 */
		pgStatLocal.snapshot.context = NULL;
	}

	/*
	 * 从历史上看，backend_status.c 的功能就存在于此文件中，
	 * 并与相同的函数一起重置。现在保持这种方式，并转发重置请求。
	 */
	pgstat_clear_backend_activity_snapshot();

	/* 重置此标志，因为可能已经强制进行了清理。 */
	force_stats_snapshot_clear = false;
}

void * pgstat_fetch_entry(PgStat_Kind fc_kind, Oid fc_dboid, Oid fc_objoid)
{
	PgStat_HashKey fc_key;
	PgStat_EntryRef *fc_entry_ref;
	void	   *fc_stats_data;
	const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);

	/* 应该从后端调用 */
	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);
	AssertArg(!fc_kind_info->fixed_amount);

	fc_pgstat_prep_snapshot();

	/* 清除填充 */
	memset(&fc_key, 0, sizeof(struct PgStat_HashKey));

	fc_key.kind = fc_kind;
	fc_key.dboid = fc_dboid;
	fc_key.objoid = fc_objoid;

	/* 如果我们需要构建完整的快照，就这样做 */
	if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT)
		fc_pgstat_build_snapshot();

	/* 如果希望进行缓存，则在缓存中查找 */
	if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE)
	{
		PgStat_SnapshotEntry *fc_entry = NULL;

		fc_entry = pgstat_snapshot_lookup(pgStatLocal.snapshot.stats, fc_key);

		if (fc_entry)
			return fc_entry->data;

		/*
		 * 如果我们构建了完整的快照，并且密钥不在
		 * pgStatLocal.snapshot.stats 中，则没有匹配的统计数据。
		 */
		if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT)
			return NULL;
	}

	pgStatLocal.snapshot.mode = pgstat_fetch_consistency;

	fc_entry_ref = pgstat_get_entry_ref(fc_kind, fc_dboid, fc_objoid, false, NULL);

	if (fc_entry_ref == NULL || fc_entry_ref->shared_entry->dropped)
	{
		/* 使用 PGSTAT_FETCH_CONSISTENCY_CACHE 时创建空条目 */
		if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_CACHE)
		{
			PgStat_SnapshotEntry *fc_entry = NULL;
			bool		fc_found;

			fc_entry = pgstat_snapshot_insert(pgStatLocal.snapshot.stats, fc_key, &fc_found);
			Assert(!fc_found);
			fc_entry->data = NULL;
		}
		return NULL;
	}

	/*
	 * 为 PGSTAT_FETCH_CONSISTENCY_NONE 在调用者的上下文中分配，
	 * 否则我们可能会由于重复访问而快速使用大量内存。
	 */
	if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_NONE)
		fc_stats_data = palloc(fc_kind_info->shared_data_len);
	else
		fc_stats_data = MemoryContextAlloc(pgStatLocal.snapshot.context,
										fc_kind_info->shared_data_len);

	pgstat_lock_entry_shared(fc_entry_ref, false);
	memcpy(fc_stats_data,
		   pgstat_get_entry_data(fc_kind, fc_entry_ref->shared_stats),
		   fc_kind_info->shared_data_len);
	pgstat_unlock_entry(fc_entry_ref);

	if (pgstat_fetch_consistency > PGSTAT_FETCH_CONSISTENCY_NONE)
	{
		PgStat_SnapshotEntry *fc_entry = NULL;
		bool		fc_found;

		fc_entry = pgstat_snapshot_insert(pgStatLocal.snapshot.stats, fc_key, &fc_found);
		fc_entry->data = fc_stats_data;
	}

	return fc_stats_data;
}

/*
 * 如果已获取统计快照，则返回该快照的时间戳，并将 *have_snapshot 设置为 true。
 * 否则，*have_snapshot 设置为 false。
 */
TimestampTz pgstat_get_stat_snapshot_timestamp(bool *fc_have_snapshot)
{
	if (force_stats_snapshot_clear)
		pgstat_clear_snapshot();

	if (pgStatLocal.snapshot.mode == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT)
	{
		*fc_have_snapshot = true;
		return pgStatLocal.snapshot.snapshot_timestamp;
	}

	*fc_have_snapshot = false;

	return 0;
}

bool pgstat_have_entry(PgStat_Kind fc_kind, Oid fc_dboid, Oid fc_objoid)
{
	/* 固定编号的统计数据始终存在 */
	if (pgstat_get_kind_info(fc_kind)->fixed_amount)
		return true;

	return pgstat_get_entry_ref(fc_kind, fc_dboid, fc_objoid, false, NULL) != NULL;
}

/*
 * 确保存在固定编号的 'kind' 的快照。
 *
 * 通常由 pgstat_fetch_* 函数用于一种统计数据，在
 * 将数据处理成所需格式之前。
 */
void pgstat_snapshot_fixed(PgStat_Kind fc_kind)
{
	AssertArg(fc_pgstat_is_kind_valid(fc_kind));
	AssertArg(pgstat_get_kind_info(fc_kind)->fixed_amount);

	if (force_stats_snapshot_clear)
		pgstat_clear_snapshot();

	if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT)
		fc_pgstat_build_snapshot();
	else
		fc_pgstat_build_snapshot_fixed(fc_kind);

	Assert(pgStatLocal.snapshot.fixed_valid[fc_kind]);
}

static void fc_pgstat_prep_snapshot(void)
{
	if (force_stats_snapshot_clear)
		pgstat_clear_snapshot();

	if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_NONE ||
		pgStatLocal.snapshot.stats != NULL)
		return;

	if (!pgStatLocal.snapshot.context)
		pgStatLocal.snapshot.context = AllocSetContextCreate(TopMemoryContext,
															 "PgStat Snapshot",
															 ALLOCSET_SMALL_SIZES);

	pgStatLocal.snapshot.stats =
		pgstat_snapshot_create(pgStatLocal.snapshot.context,
							   PGSTAT_SNAPSHOT_HASH_SIZE,
							   NULL);
}

static void fc_pgstat_build_snapshot(void)
{
	dshash_seq_status fc_hstat;
	PgStatShared_HashEntry *fc_p;

	/* 仅在需要快照时调用 */
	Assert(pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT);

	/* 快照已构建 */
	if (pgStatLocal.snapshot.mode == PGSTAT_FETCH_CONSISTENCY_SNAPSHOT)
		return;

	fc_pgstat_prep_snapshot();

	Assert(pgStatLocal.snapshot.stats->members == 0);

	pgStatLocal.snapshot.snapshot_timestamp = GetCurrentTimestamp();

	/*
	 * 快照所有变量统计数据。
	 */
	dshash_seq_init(&fc_hstat, pgStatLocal.shared_hash, false);
	while ((fc_p = dshash_seq_next(&fc_hstat)) != NULL)
	{
		PgStat_Kind fc_kind = fc_p->key.kind;
		const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);
		bool		fc_found;
		PgStat_SnapshotEntry *fc_entry;
		PgStatShared_Common *fc_stats_data;

		/*
		 * 检查统计对象是否应包含在快照中。
		 * 除非统计种类可以从所有数据库访问（例如，
		 * 数据库统计本身），否则我们只包括当前
		 * 数据库的统计数据或与数据库无关的对象（例如共享
		 * 关系）。
		 */
		if (fc_p->key.dboid != MyDatabaseId &&
			fc_p->key.dboid != InvalidOid &&
			!fc_kind_info->accessed_across_databases)
			continue;

		if (fc_p->dropped)
			continue;

		Assert(pg_atomic_read_u32(&fc_p->refcount) > 0);

		fc_stats_data = dsa_get_address(pgStatLocal.dsa, fc_p->body);
		Assert(fc_stats_data);

		fc_entry = pgstat_snapshot_insert(pgStatLocal.snapshot.stats, fc_p->key, &fc_found);
		Assert(!fc_found);

		fc_entry->data = MemoryContextAlloc(pgStatLocal.snapshot.context,
										 fc_kind_info->shared_size);
		/*
		 * 直接获取 LWLock，而不是使用
		 * pg_stat_lock_entry_shared()，后者需要一个引用。
		 */
		LWLockAcquire(&fc_stats_data->lock, LW_SHARED);
		memcpy(fc_entry->data,
			   pgstat_get_entry_data(fc_kind, fc_stats_data),
			   fc_kind_info->shared_size);
		LWLockRelease(&fc_stats_data->lock);
	}
	dshash_seq_term(&fc_hstat);

	/*
	 * 构建所有固定编号统计数据的快照。
	 */
	for (int fc_kind = PGSTAT_KIND_FIRST_VALID; fc_kind <= PGSTAT_KIND_LAST; fc_kind++)
	{
		const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);

		if (!fc_kind_info->fixed_amount)
		{
			Assert(fc_kind_info->snapshot_cb == NULL);
			continue;
		}

		fc_pgstat_build_snapshot_fixed(fc_kind);
	}

	pgStatLocal.snapshot.mode = PGSTAT_FETCH_CONSISTENCY_SNAPSHOT;
}

static void fc_pgstat_build_snapshot_fixed(PgStat_Kind fc_kind)
{
	const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);

	Assert(fc_kind_info->fixed_amount);
	Assert(fc_kind_info->snapshot_cb != NULL);

	if (pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_NONE)
	{
		/* 每次都重建 */
		pgStatLocal.snapshot.fixed_valid[fc_kind] = false;
	}
	else if (pgStatLocal.snapshot.fixed_valid[fc_kind])
	{
		/* 在快照模式下我们不应该再次被调用 */
		Assert(pgstat_fetch_consistency == PGSTAT_FETCH_CONSISTENCY_CACHE);
		return;
	}

	Assert(!pgStatLocal.snapshot.fixed_valid[fc_kind]);

	fc_kind_info->snapshot_cb();

	Assert(!pgStatLocal.snapshot.fixed_valid[fc_kind]);
	pgStatLocal.snapshot.fixed_valid[fc_kind] = true;
}


/* ------------------------------------------------------------
 * 后端本地待处理统计基础设施
 * ------------------------------------------------------------
 */

/*
 * 返回适当的 PgStat_EntryRef，准备好接收待处理
 * 统计数据（如果尚未完成）。
 *
 * 如果 created_entry 非 NULL，则如果条目是新创建的，则将其设置为 true，反之则为 false。
 */
PgStat_EntryRef * pgstat_prep_pending_entry(PgStat_Kind fc_kind, Oid fc_dboid, Oid fc_objoid, bool *fc_created_entry)
{
	PgStat_EntryRef *fc_entry_ref;

	/* 需要能够刷新 */
	Assert(pgstat_get_kind_info(fc_kind)->flush_pending_cb != NULL);

	if (unlikely(!pgStatPendingContext))
	{
		pgStatPendingContext =
			AllocSetContextCreate(TopMemoryContext,
								  "PgStat Pending",
								  ALLOCSET_SMALL_SIZES);
	}

	fc_entry_ref = pgstat_get_entry_ref(fc_kind, fc_dboid, fc_objoid,
									 true, fc_created_entry);

	if (fc_entry_ref->pending == NULL)
	{
		size_t		fc_entrysize = pgstat_get_kind_info(fc_kind)->pending_size;

		Assert(fc_entrysize != (size_t) -1);

		fc_entry_ref->pending = MemoryContextAllocZero(pgStatPendingContext, fc_entrysize);
		dlist_push_tail(&pgStatPending, &fc_entry_ref->pending_node);
	}

	return fc_entry_ref;
}

/*
 * 返回现有的统计条目，或 NULL。
 *
 * 这仅应用于 pgstatfuncs.c 的辅助函数 - 在那之外不应需要。
 */
PgStat_EntryRef * pgstat_fetch_pending_entry(PgStat_Kind fc_kind, Oid fc_dboid, Oid fc_objoid)
{
	PgStat_EntryRef *fc_entry_ref;

	fc_entry_ref = pgstat_get_entry_ref(fc_kind, fc_dboid, fc_objoid, false, NULL);

	if (fc_entry_ref == NULL || fc_entry_ref->pending == NULL)
		return NULL;

	return fc_entry_ref;
}

void pgstat_delete_pending_entry(PgStat_EntryRef *fc_entry_ref)
{
	PgStat_Kind fc_kind = fc_entry_ref->shared_entry->key.kind;
	const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);
	void	   *fc_pending_data = fc_entry_ref->pending;

	Assert(fc_pending_data != NULL);
	/* !fixed_amount stats 应该显式处理 */
	Assert(!pgstat_get_kind_info(fc_kind)->fixed_amount);

	if (fc_kind_info->delete_pending_cb)
		fc_kind_info->delete_pending_cb(fc_entry_ref);

	pfree(fc_pending_data);
	fc_entry_ref->pending = NULL;

	dlist_delete(&fc_entry_ref->pending_node);
}

/*
 * 刷新数据库对象（数据库、关系、函数）的待处理统计信息。
 */
static bool fc_pgstat_flush_pending_entries(bool fc_nowait)
{
	bool		fc_have_pending = false;
	dlist_node *fc_cur = NULL;

	/*
	 * 在遍历待处理条目列表时需要小心一点。
	 * 处理一个待处理条目可能会将更多的待处理条目排队到我们想处理的列表末尾，
	 * 所以简单的迭代并不够。
	 * 更复杂的是，我们希望在每次迭代中，如果成功刷新，则从列表中删除当前条目。
	 *
	 * 所以我们只需在每个循环迭代中跟踪下一个指针。
	 */
	if (!dlist_is_empty(&pgStatPending))
		fc_cur = dlist_head_node(&pgStatPending);

	while (fc_cur)
	{
		PgStat_EntryRef *fc_entry_ref =
		dlist_container(PgStat_EntryRef, pending_node, fc_cur);
		PgStat_HashKey fc_key = fc_entry_ref->shared_entry->key;
		PgStat_Kind fc_kind = fc_key.kind;
		const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);
		bool		fc_did_flush;
		dlist_node *fc_next;

		Assert(!fc_kind_info->fixed_amount);
		Assert(fc_kind_info->flush_pending_cb != NULL);

		/* 如果可能，刷新统计信息 */
		fc_did_flush = fc_kind_info->flush_pending_cb(fc_entry_ref, fc_nowait);

		Assert(fc_did_flush || fc_nowait);

		/* 在删除待处理条目之前确定下一个条目 */
		if (dlist_has_next(&pgStatPending, fc_cur))
			fc_next = dlist_next_node(&pgStatPending, fc_cur);
		else
			fc_next = NULL;

		/* 如果成功刷新，移除条目 */
		if (fc_did_flush)
			pgstat_delete_pending_entry(fc_entry_ref);
		else
			fc_have_pending = true;

		fc_cur = fc_next;
	}

	Assert(dlist_is_empty(&pgStatPending) == !fc_have_pending);

	return fc_have_pending;
}


/* ------------------------------------------------------------
 * 辅助 / 基础设施函数
 * ------------------------------------------------------------
 */

PgStat_Kind
pgstat_get_kind_from_str(char *fc_kind_str)
{
	for (int fc_kind = PGSTAT_KIND_FIRST_VALID; fc_kind <= PGSTAT_KIND_LAST; fc_kind++)
	{
		if (pg_strcasecmp(fc_kind_str, pgstat_kind_infos[fc_kind].name) == 0)
			return fc_kind;
	}

	ereport(ERROR,
			(errcode(ERRCODE_INVALID_PARAMETER_VALUE),
			 errmsg("invalid statistics kind: \"%s\"", fc_kind_str)));
	return PGSTAT_KIND_DATABASE;	/* 避免编译器警告 */
}

static inline bool fc_pgstat_is_kind_valid(int fc_ikind)
{
	return fc_ikind >= PGSTAT_KIND_FIRST_VALID && fc_ikind <= PGSTAT_KIND_LAST;
}

const PgStat_KindInfo *
pgstat_get_kind_info(PgStat_Kind fc_kind)
{
	AssertArg(fc_pgstat_is_kind_valid(fc_kind));

	return &pgstat_kind_infos[fc_kind];
}

/*
 * 仅在 pgstat_initialize() 之后和 pgstat_shutdown() 之前报告统计信息。
 * 此检查放在一些中心位置，以更容易捕捉到此规则的违反。
 */
#ifdef USE_ASSERT_CHECKING
void pgstat_assert_is_up(void)
{
	Assert(pgstat_is_initialized && !pgstat_is_shutdown);
}
#endif


/* ------------------------------------------------------------
 * 读取和写入磁盘上的统计文件
 * ------------------------------------------------------------
 */

/* pgstat_write_statsfile() 的助手函数 */
static void fc_write_chunk(FILE *fc_fpout, void *fc_ptr, size_t fc_len)
{
	int			fc_rc;

	fc_rc = fwrite(fc_ptr, fc_len, 1, fc_fpout);

	/* 我们将在最后检查 ferror 是否出现错误 */
	(void) fc_rc;
}

#define write_chunk_s(fpout, ptr) fc_write_chunk(fpout, ptr, sizeof(*ptr))

/*
 * 此函数在最后一个访问共享统计信息的进程中被调用，因此不需要锁定。
 */
static void fc_pgstat_write_statsfile(void)
{
	FILE	   *fc_fpout;
	int32		fc_format_id;
	const char *fc_tmpfile = PGSTAT_STAT_PERMANENT_TMPFILE;
	const char *fc_statfile = PGSTAT_STAT_PERMANENT_FILENAME;
	dshash_seq_status fc_hstat;
	PgStatShared_HashEntry *fc_ps;

	pgstat_assert_is_up();

	/* 我们正在关闭，所以可以直接覆盖此项 */
	pgstat_fetch_consistency = PGSTAT_FETCH_CONSISTENCY_NONE;

	elog(DEBUG2, "writing stats file \"%s\"", fc_statfile);

	/*
	 * 打开统计临时文件以写入当前值。
	 */
	fc_fpout = AllocateFile(fc_tmpfile, PG_BINARY_W);
	if (fc_fpout == NULL)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not open temporary statistics file \"%s\": %m",
						fc_tmpfile)));
		return;
	}

	/*
	 * 写入文件头 --- 当前只是一个格式 ID。
	 */
	fc_format_id = PGSTAT_FILE_FORMAT_ID;
	write_chunk_s(fc_fpout, &fc_format_id);

	/*
	 * XXX: 下面的内容现在可以泛化为仅遍历
	 * pgstat_kind_infos，而不必了解不同种类的
	 * 统计信息。
	 */

	/*
	 * 写入 archiver 统计结构
	 */
	fc_pgstat_build_snapshot_fixed(PGSTAT_KIND_ARCHIVER);
	write_chunk_s(fc_fpout, &pgStatLocal.snapshot.archiver);

	/*
	 * 写入 bgwriter 统计结构
	 */
	fc_pgstat_build_snapshot_fixed(PGSTAT_KIND_BGWRITER);
	write_chunk_s(fc_fpout, &pgStatLocal.snapshot.bgwriter);

	/*
	 * 写入 checkpointer 统计结构
	 */
	fc_pgstat_build_snapshot_fixed(PGSTAT_KIND_CHECKPOINTER);
	write_chunk_s(fc_fpout, &pgStatLocal.snapshot.checkpointer);

	/*
	 * 写入 SLRU 统计结构
	 */
	fc_pgstat_build_snapshot_fixed(PGSTAT_KIND_SLRU);
	write_chunk_s(fc_fpout, &pgStatLocal.snapshot.slru);

	/*
	 * 写入 WAL 统计结构
	 */
	fc_pgstat_build_snapshot_fixed(PGSTAT_KIND_WAL);
	write_chunk_s(fc_fpout, &pgStatLocal.snapshot.wal);

	/*
	 * 遍历统计条目
	 */
	dshash_seq_init(&fc_hstat, pgStatLocal.shared_hash, false);
	while ((fc_ps = dshash_seq_next(&fc_hstat)) != NULL)
	{
		PgStatShared_Common *fc_shstats;
		const PgStat_KindInfo *fc_kind_info = NULL;

		CHECK_FOR_INTERRUPTS();

		/*
		 * 在写入统计文件时，我们不应该看到任何“丢弃”的条目，
		 * 因为所有的后端和辅助进程都应该在终止之前清理
		 * 它们的引用。
		 *
		 * 然而，由于我们已经在关闭，不值得因为任何潜在的清理问题
		 * 而让服务器崩溃，因此如果遇到这样的条目，我们只需跳过它们。
		 */
		Assert(!fc_ps->dropped);
		if (fc_ps->dropped)
			continue;

		fc_shstats = (PgStatShared_Common *) dsa_get_address(pgStatLocal.dsa, fc_ps->body);

		fc_kind_info = pgstat_get_kind_info(fc_ps->key.kind);

		/* 如果未丢弃，有效条目的引用计数应该存在 */
		Assert(pg_atomic_read_u32(&fc_ps->refcount) > 0);

		if (!fc_kind_info->to_serialized_name)
		{
			/* 正常统计条目，通过 PgStat_HashKey 标识 */
			fputc('S', fc_fpout);
			write_chunk_s(fc_fpout, &fc_ps->key);
		}
		else
		{
			/* 通过磁盘上的名称（例如槽）识别的统计条目 */
			NameData	fc_name;

			fc_kind_info->to_serialized_name(&fc_ps->key, fc_shstats, &fc_name);

			fputc('N', fc_fpout);
			write_chunk_s(fc_fpout, &fc_ps->key.kind);
			write_chunk_s(fc_fpout, &fc_name);
		}

		/* 写入条目的所有部分，除头部部分外 */
		fc_write_chunk(fc_fpout,
					pgstat_get_entry_data(fc_ps->key.kind, fc_shstats),
					pgstat_get_entry_len(fc_ps->key.kind));
	}
	dshash_seq_term(&fc_hstat);

	/*
	 * 不再进行输出。关闭临时文件并用其替换旧的
	 * pgstat.stat。ferror() 检查替代了在上面的 write_chunk() 中
	 * 对每个单独的 fputc 或 fwrite 进行错误测试。
	 */
	fputc('E', fc_fpout);

	if (ferror(fc_fpout))
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not write temporary statistics file \"%s\": %m",
						fc_tmpfile)));
		FreeFile(fc_fpout);
		unlink(fc_tmpfile);
	}
	else if (FreeFile(fc_fpout) < 0)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not close temporary statistics file \"%s\": %m",
						fc_tmpfile)));
		unlink(fc_tmpfile);
	}
	else if (rename(fc_tmpfile, fc_statfile) < 0)
	{
		ereport(LOG,
				(errcode_for_file_access(),
				 errmsg("could not rename temporary statistics file \"%s\" to \"%s\": %m",
						fc_tmpfile, fc_statfile)));
		unlink(fc_tmpfile);
	}
}

/* pgstat_read_statsfile() 的辅助函数 */
static bool fc_read_chunk(FILE *fc_fpin, void *fc_ptr, size_t fc_len)
{
	return fread(fc_ptr, 1, fc_len, fc_fpin) == fc_len;
}

#define read_chunk_s(fpin, ptr) fc_read_chunk(fpin, ptr, sizeof(*ptr))

/*
 * 将现有统计信息文件读入共享统计信息哈希中。
 *
 * 此函数在唯一访问共享统计信息的进程中调用，因此无需加锁。
 */
static void fc_pgstat_read_statsfile(void)
{
	FILE	   *fc_fpin;
	int32		fc_format_id;
	bool		fc_found;
	const char *fc_statfile = PGSTAT_STAT_PERMANENT_FILENAME;
	PgStat_ShmemControl *fc_shmem = pgStatLocal.shmem;

	/* 不应从 postmaster 调用 */
	Assert(IsUnderPostmaster || !IsPostmasterEnvironment);

	elog(DEBUG2, "reading stats file \"%s\"", fc_statfile);

	/*
	 * 尝试打开统计信息文件。如果文件不存在，后端会简单地
	 * 对任何内容返回零，统计信息将从空计数器开始重新开始。
	 *
	 * 如果之前禁用了统计信息收集或尚未第一次写入统计信息文件，
	 * 则 ENOENT 是可能发生的情况。任何其他失败条件都值得怀疑。
	 */
	if ((fc_fpin = AllocateFile(fc_statfile, PG_BINARY_R)) == NULL)
	{
		if (errno != ENOENT)
			ereport(LOG,
					(errcode_for_file_access(),
					 errmsg("could not open statistics file \"%s\": %m",
							fc_statfile)));
		fc_pgstat_reset_after_failure();
		return;
	}

	/*
	 * 验证其是否符合预期格式。
	 */
	if (!read_chunk_s(fc_fpin, &fc_format_id) ||
		fc_format_id != PGSTAT_FILE_FORMAT_ID)
		goto error;

	/*
	 * XXX: 下面的内容现在可以泛化为仅遍历
	 * pgstat_kind_infos，而不必了解不同种类的
	 * 统计信息。
	 */

	/*
	 * 读取归档器统计信息结构
	 */
	if (!read_chunk_s(fc_fpin, &fc_shmem->archiver.stats))
		goto error;

	/*
	 * 读取 bgwriter 统计信息结构
	 */
	if (!read_chunk_s(fc_fpin, &fc_shmem->bgwriter.stats))
		goto error;

	/*
	 * 读取检查点生成器统计信息结构
	 */
	if (!read_chunk_s(fc_fpin, &fc_shmem->checkpointer.stats))
		goto error;

	/*
	 * 读取 SLRU 统计信息结构
	 */
	if (!read_chunk_s(fc_fpin, &fc_shmem->slru.stats))
		goto error;

	/*
	 * 读取 WAL 统计信息结构
	 */
	if (!read_chunk_s(fc_fpin, &fc_shmem->wal.stats))
		goto error;

	/*
	 * 我们找到了一个现有的统计信息文件。读取它并将所有哈希
	 * 表条目放置到位。
	 */
	for (;;)
	{
		int			fc_t = fgetc(fc_fpin);

		switch (fc_t)
		{
			case 'S':
			case 'N':
				{
					PgStat_HashKey fc_key;
					PgStatShared_HashEntry *fc_p;
					PgStatShared_Common *fc_header;

					CHECK_FOR_INTERRUPTS();

					if (fc_t == 'S')
					{
						/* 正常统计条目，通过 PgStat_HashKey 标识 */
						if (!read_chunk_s(fc_fpin, &fc_key))
							goto error;

						if (!fc_pgstat_is_kind_valid(fc_key.kind))
							goto error;
					}
					else
					{
						/* 通过磁盘上的名称（例如槽）识别的统计条目 */
						const PgStat_KindInfo *fc_kind_info = NULL;
						PgStat_Kind fc_kind;
						NameData	fc_name;

						if (!read_chunk_s(fc_fpin, &fc_kind))
							goto error;
						if (!read_chunk_s(fc_fpin, &fc_name))
							goto error;
						if (!fc_pgstat_is_kind_valid(fc_kind))
							goto error;

						fc_kind_info = pgstat_get_kind_info(fc_kind);

						if (!fc_kind_info->from_serialized_name)
							goto error;

						if (!fc_kind_info->from_serialized_name(&fc_name, &fc_key))
						{
							/* 跳过我们不关心的条目的数据 */
							if (fseek(fc_fpin, pgstat_get_entry_len(fc_kind), SEEK_CUR) != 0)
								goto error;

							continue;
						}

						Assert(fc_key.kind == fc_kind);
					}

					/*
					 * 这故意不使用 pgstat_get_entry_ref() -
					 * 将所有统计信息放入检查点生成器的
					 * pgStatEntryRefHash 将是徒劳的努力和内存浪费。
					 */
					fc_p = dshash_find_or_insert(pgStatLocal.shared_hash, &fc_key, &fc_found);

					/* 不允许重复条目 */
					if (fc_found)
					{
						dshash_release_lock(pgStatLocal.shared_hash, fc_p);
						elog(WARNING, "found duplicate stats entry %d/%u/%u",
							 fc_key.kind, fc_key.dboid, fc_key.objoid);
						goto error;
					}

					fc_header = pgstat_init_entry(fc_key.kind, fc_p);
					dshash_release_lock(pgStatLocal.shared_hash, fc_p);

					if (!fc_read_chunk(fc_fpin,
									pgstat_get_entry_data(fc_key.kind, fc_header),
									pgstat_get_entry_len(fc_key.kind)))
						goto error;

					break;
				}
			case 'E':
				/* 检查 'E' 是否实际表示文件结束 */
				if (fgetc(fc_fpin) != EOF)
					goto error;

				goto done;

			default:
				goto error;
		}
	}

done:
	FreeFile(fc_fpin);

	elog(DEBUG2, "removing permanent stats file \"%s\"", fc_statfile);
	unlink(fc_statfile);

	return;

error:
	ereport(LOG,
			(errmsg("corrupted statistics file \"%s\"", fc_statfile)));

	fc_pgstat_reset_after_failure();

	goto done;
}

/*
 * 在崩溃后或从磁盘恢复统计信息失败后重置/删除统计信息的助手，
 * 可能在已经加载部分内容之后进行。
 */
static void fc_pgstat_reset_after_failure(void)
{
	TimestampTz fc_ts = GetCurrentTimestamp();

	/* 重置固定编号统计信息 */
	for (int fc_kind = PGSTAT_KIND_FIRST_VALID; fc_kind <= PGSTAT_KIND_LAST; fc_kind++)
	{
		const PgStat_KindInfo *fc_kind_info = pgstat_get_kind_info(fc_kind);

		if (!fc_kind_info->fixed_amount)
			continue;

		fc_kind_info->reset_all_cb(fc_ts);
	}

	/* 并删除可变编号的统计信息 */
	pgstat_drop_all_entries();
}

/*
 * 统计信息获取一致性的 GUC assign_hook。
 */
void assign_stats_fetch_consistency(int fc_newval, void *fc_extra)
{
	/*
	 * 在事务中更改此值可能会导致快照状态
	 * 不一致，因此在下一个快照构建尝试时强制清除当前快照。
	 */
	if (pgstat_fetch_consistency != fc_newval)
		force_stats_snapshot_clear = true;
}
